﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using VetumaForSharePoint.Configuration;

namespace VetumaForSharePoint
{
    public class VFSHasher
    {
        /// <summary>
        /// Hashes string.
        /// </summary>
        /// <param name="textToHash">Text that needs to be hashed.</param>
        /// <returns>TextToHash-param as hashed string.</returns>
        internal static string computeHash(string textToHash)
        {
            byte[] saltBytes = getIv(VetumaForSharePointConfig.getConfig().RegistrationConfig.VFSKey.ToByteArray());

            // Get bytes from text to hash
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(textToHash);

            // Create byte array where to add plain text bytes and salt bytes
            byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length];

            // Add plain text bytes
            for (int i = 0; i < plainTextBytes.Length; i++)
            {
                plainTextWithSaltBytes[i] = plainTextBytes[i];
            }

            // Add salt bytes
            for (int i = 0; i < saltBytes.Length; i++)
            {
                plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];
            }

            HashAlgorithm hash = new MD5CryptoServiceProvider();

            // Hash bytes
            byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);

            // If you want to include salt to hash value, remove comments from next lines
            // and change string hashValue = Convert.ToBase64String(hashBytes); to
            // string hashValue = Convert.ToBase64String(hashWithSaltBytes);

            //byte[] hashWithSaltBytes = new byte[hashBytes.Length +
            //                                    saltBytes.Length];

            //for (int i = 0; i < hashBytes.Length; i++)
            //    hashWithSaltBytes[i] = hashBytes[i];

            //for (int i = 0; i < saltBytes.Length; i++)
            //    hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];

            // Convert bytes to string
            string hashValue = Convert.ToBase64String(hashBytes);

            return hashValue;
        }

        ///// <summary>
        ///// Hashes a string.
        ///// </summary>
        ///// <param name="textToHash">String to hash.</param>
        ///// <param name="saltBytes">Salt bytes. Can be null.</param>
        ///// <returns>PlainText as hashed string.</returns>
        //internal static string ComputeHash(string textToHash, byte[] saltBytes)
        //{
        //    // If salt is not specified, generate it.
        //    if (saltBytes == null)
        //    {
        //        int minSaltSize = 4;
        //        int maxSaltSize = 8;

        //        // Generate a random number for the size of the salt.
        //        Random random = new Random();
        //        int saltSize = random.Next(minSaltSize, maxSaltSize);

        //        // Create new byte array to hold the salt.
        //        saltBytes = new byte[saltSize];

        //        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        //        // Fill the salt with cryptographically strong byte values.
        //        rng.GetNonZeroBytes(saltBytes);
        //    }

        //    // Get bytes from text to hash.
        //    byte[] textToHashBytes = Encoding.UTF8.GetBytes(textToHash);

        //    // Create new byte array, which holds plain text and salt.
        //    byte[] textToHashWithSaltBytes = new byte[textToHashBytes.Length + saltBytes.Length];

        //    // Copy text to hash bytes.
        //    for (int i = 0; i < textToHashBytes.Length; i++)
        //    {
        //        textToHashWithSaltBytes[i] = textToHashBytes[i];
        //    }

        //    // Append salt bytes.
        //    for (int i = 0; i < saltBytes.Length; i++)
        //    {
        //        textToHashWithSaltBytes[textToHashBytes.Length + i] = saltBytes[i];
        //    }

        //    HashAlgorithm hash = new SHA1Managed();

        //    // Hash bytes.
        //    byte[] hashBytes = hash.ComputeHash(textToHashWithSaltBytes);

        //    // Create new array which holds hashed bytes and original salt bytes.
        //    byte[] hashWithSaltBytes = new byte[hashBytes.Length + saltBytes.Length];

        //    // Copy hash bytes.
        //    for (int i = 0; i < hashBytes.Length; i++)
        //    {
        //        hashWithSaltBytes[i] = hashBytes[i];
        //    }

        //    // Append salt bytes.
        //    for (int i = 0; i < saltBytes.Length; i++)
        //    {
        //        hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];
        //    }

        //    // Convert result into a base64-encoded string.
        //    string hashValue = Convert.ToBase64String(hashWithSaltBytes);

        //    // Return the result.
        //    return hashValue;
        //}

        ///// <summary>
        ///// Verifies hash.
        ///// </summary>
        ///// <param name="plainText">Text that hashValue should contain.</param>
        ///// <param name="hashValue">Hashed value from plain text.</param>
        ///// <returns></returns>
        //internal static bool VerifyHash(string plainText, string hashValue)
        //{
        //    // Convert hash value into a byte array.
        //    byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue);

        //    // Size of the hash without salt.
        //    int hashSizeInBytes = 20;

        //    // Make sure that the specified hash value is long enough.
        //    if (hashWithSaltBytes.Length < hashSizeInBytes)
        //    {
        //        return false;
        //    }

        //    // Create new byte array to hold the original salt bytes retrieved from hash.
        //    byte[] saltBytes = new byte[hashWithSaltBytes.Length - hashSizeInBytes];

        //    // Copy salt from the end of the hash to the new array.
        //    for (int i = 0; i < saltBytes.Length; i++)
        //    {
        //        saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];
        //    }

        //    // Compute a new hash string.
        //    string expectedHashString = ComputeHash(plainText, saltBytes);

        //    // Compare hashes.
        //    return (hashValue == expectedHashString);
        //}

        /// <summary>
        /// Encrypts string.
        /// </summary>
        /// <param name="textToEncrypt">String to encrypt.</param>
        /// <param name="vfsKey">VFSKey from web.config.</param>
        /// <returns>Encrypted string.</returns>
        internal static string Encrypt(string textToEncrypt, Guid vfsKey)
        {
            byte[] key = vfsKey.ToByteArray();
            byte[] iv = getIv(key);

            byte[] textToEncryptBytes = Encoding.UTF8.GetBytes(textToEncrypt);

            TripleDES alg = TripleDES.Create();

            alg.Key = key;
            alg.IV = iv;

            byte[] encryptedData;

            // Create a MemoryStream to accept the encrypted bytes 
            using (MemoryStream memStream = new MemoryStream())
            {
                using (CryptoStream crypStream = new CryptoStream(memStream, alg.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    crypStream.Write(textToEncryptBytes, 0, textToEncryptBytes.Length);
                    crypStream.Close();
                };

                encryptedData = memStream.ToArray();
            };

            return Convert.ToBase64String(encryptedData);
        }

        /// <summary>
        /// Decrypts encrypted string.
        /// </summary>
        /// <param name="textToEncrypt">Encrypted string to decrypt.</param>
        /// <param name="vfsKey">VFSKey from web.config.</param>
        /// <returns>Decrypted string of encrypted string.</returns>
        internal static string Decrypt(string textToDecrypt, Guid vfsKey)
        {
            byte[] key = vfsKey.ToByteArray();
            byte[] iv = getIv(key);

            byte[] textToDecryptBytes = Convert.FromBase64String(textToDecrypt);

            TripleDES alg = TripleDES.Create();

            alg.Key = key;
            alg.IV = iv;

            // Create a MemoryStream to accept the encrypted bytes 
            using (MemoryStream memStream = new MemoryStream(textToDecryptBytes))
            {
                using (CryptoStream crypStream = new CryptoStream(memStream, alg.CreateDecryptor(), CryptoStreamMode.Read))
                {
                    StreamReader reader = new StreamReader(crypStream);
                    return reader.ReadToEnd();
                };
            };
        }

        /// <summary>
        /// Creates IV for encryption and decryption.
        /// </summary>
        /// <param name="key">Bytes from VFSKey in web.config.</param>
        /// <returns>Byte array used as IV for encryption and decryption.</returns>
        private static byte[] getIv(byte[] key)
        {
            byte[] ivBytes = new byte[] { key[0],
                                          key[1],
                                          key[4],
                                          key[9],
                                          key[10],
                                          key[11],
                                          key[13],
                                          key[15] };

            return ivBytes;
        }

    }
}
